#include <multiboot.h>
#include <libk/stdio.h>
#include <libk/x86.h>
#include <libk/string.h>

unsigned long maxmem = 0;

void *testmem = (void *) 0x40000000;

unsigned char *ram = (unsigned char *) 0xC0000500;

void parse_mmap(multiboot_info_t *header)
{
	memory_map_t *mmap = (memory_map_t *) (header->mmap_addr + 0xC0000000);

	while (((unsigned long) mmap) + mmap->size < header->mmap_addr + header->mmap_length + 0xC0000000)
	{
		if ((mmap->base_addr_low + mmap->length_low) > maxmem)
		{
			maxmem = mmap->base_addr_low + mmap->length_low;
		}
		unsigned long addr = mmap->base_addr_low / 0x1000;
		unsigned long limit = mmap->length_low / 0x1000;
		//printf("Memory Map Block:\n\tAddr: 0x%x\n\tBase: 0x%x\n\tLeng: 0x%x\n\tType: 0x%x\n\tSize: 0x%x\n\n", mmap, mmap->base_addr_low, mmap->length_low, mmap->type, mmap->size);
		while (addr < 0x120 && limit > 0)
		{
			addr++;
			limit--;
		}
		switch (mmap->type)
		{
			case 1:
				while (limit > 0)
				{
					ram[addr/8] |= 1 << addr % 8;
					limit--;
					addr++;
				}
			case 2:
			case 3:
				mmap = (memory_map_t *) (((unsigned long) mmap) + mmap->size + 4);
				//printf("Addr: 0x%x\n", mmap);
				break;
			default:
				return;
		}
	}
}

void *get_phys_page()
{
	unsigned char tester = 1;
	unsigned char setter = 0xFF;
	static unsigned long count = 0;
	unsigned char i=0;
	while (1)
	{
		if ((ram[count] & tester) == tester)
		{
			setter -= tester;
			ram[count] &= setter;
			while (tester > 1)
			{
				i++;
				tester /= 2;
			}
			return (void *) ((((count * 8) + i)  * 0x1000));
		}
		if (tester == 128)
		{
			tester = 1;
			count++;
		}
		else
		{
			tester <<= 1;
		}
		if (count * 0x1000 >= maxmem)
		{
			return (void *) -1;
		}
	}
	return (void *) -1;
}

void give_phys_page(void *page)
{
	unsigned long count = (((unsigned long) page) / 0x8000);
	unsigned char bit = ((((unsigned long) page) / 0x1000) % 8);

	ram[count] |= bit;

	return;
}

void map_page(void *physaddr, void *virtaddr, unsigned int flags)
{
	unsigned long pdindex = (unsigned long) virtaddr >> 22;
	unsigned long ptindex = (unsigned long) virtaddr >> 12 & 0x3FF;

	unsigned long *pd = (unsigned long *) 0xFFFFF000;
	if ((pd[pdindex] & 1) != 1)
	{
		pd[pdindex] = (unsigned long) get_phys_page() | 0x3;
	}

	unsigned long *pt = (unsigned long *) 0xFFC00000 + (0x400 * pdindex);
	if ((pt[ptindex] & 1) == 1)
	{
		printf("System Failure!\n\tPT: 0x%x\t\tnum: 0x%x", pdindex, ptindex);
		cli();
		hlt();
	}

	pt[ptindex] = ((unsigned long)physaddr) | (flags & 0xFFF) | 0x01;
}

void *unmap_page(void *virtaddr)
{
	unsigned long pdindex = (unsigned long) virtaddr >> 22;
	unsigned long ptindex = (unsigned long) virtaddr >> 12 & 0x3FF;

	unsigned long *pt = (unsigned long *) 0xFFC00000 + (0x400 * pdindex);

	if ((pt[ptindex] & 1) == 1)
	{
		printf("ERROR!\n\n\tPAGE NOT MAPPED!");
		cli();
		hlt();
	}

	pt[ptindex] &= 0xFFFFFFFE;
	return (void *) (pt[ptindex] & 0xFFFFF000);
}

int liballoc_lock()
{
	cli();
	return 0;
}

int liballoc_unlock()
{
	sti();
	return 0;
}

void *liballoc_alloc(size_t num)
{
	void *retval = testmem;
	while (num > 0)
	{
		map_page(get_phys_page(), testmem, 0x3);
		testmem += 0x1000;
		num--;
	}
	return retval;
}

int liballoc_free(void *addr, size_t num)
{
	while (num > 0)
	{
		give_phys_page(unmap_page(addr));
		addr += 0x1000;
		num--;
	}
	return 0;
}
